package com.love.marriage.userInfo.service.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.love.marriage.TecentIm.service.ChatService;
import com.love.marriage.cloud.vo.RedPacketVo;
import com.love.marriage.commissionInfo.service.CommissionInfoService;
import com.love.marriage.commissionInfo.vo.CommissionInfoVo;
import com.love.marriage.common.constants.RedisConstants;
import com.love.marriage.common.constants.SysConf;
import com.love.marriage.common.enums.EStatus;
import com.love.marriage.common.enums.ResultEnum;
import com.love.marriage.common.event.UserInviteRedPacketEvent;
import com.love.marriage.common.exceptions.LoginException;
import com.love.marriage.common.redisKey.LoginKey;
import com.love.marriage.common.vo.Audience;
import com.love.marriage.common.vo.Result;
import com.love.marriage.common.vo.SecurityUserFactory;
import com.love.marriage.config.WeChatConfig;
import com.love.marriage.defaultAmountConfig.mapper.DefaultAmountConfigMapper;
import com.love.marriage.defaultAmountConfig.vo.DefaultAmountConfigVo;
import com.love.marriage.userInfo.entity.UserInfo;
import com.love.marriage.userInfo.mapper.UserInfoMapper;
import com.love.marriage.userInfo.service.LoginService;
import com.love.marriage.userInfo.service.UserInfoService;
import com.love.marriage.userInfo.vo.UserInfoVo;
import com.love.marriage.userInfo.vo.UserVo;
import com.love.marriage.userLoginInfo.entity.UserLoginInfo;
import com.love.marriage.userProfileInfo.service.UserProfileInfoService;
import com.love.marriage.userProfileInfo.vo.UserProfileInfoVo;
import com.love.marriage.utils.IpUtils;
import com.love.marriage.utils.JwtTokenUtils;
import com.love.marriage.utils.RedisUtils;
import com.love.marriage.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
@Slf4j
public class LoginServiceImpl implements LoginService {

    private final JwtTokenUtils jwtTokenUtils;

    private final RedisUtils redisUtils;

    private final Audience audience;

    private final Executor taskExecutor;

    private final UserInfoService userInfoService;

    private static final String ERROR_MESSAGE = "用户名或密码错误，错误%d次后，账户将被锁定30分钟";
    private final UserInfoMapper userInfoMapper;

    @Value(value = "${isRememberMeExpiresSecond}")
    private int isRememberMeExpiresSecond;

    private final WeChatConfig weChatConfig;

    private final UserProfileInfoService userProfileInfoService;

    private final ChatService chatService;

    private final WxMaService wxMaService;

    private final DefaultAmountConfigMapper defaultAmountConfigMapper;

    private final ApplicationEventPublisher eventPublisher;

    private final CommissionInfoService commissionInfoService;

    @Override
    public UserVo login(HttpServletRequest request, String username, String password, Boolean isRemember, Long instrId, String source) throws Exception {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("开始登录");
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            throw new LoginException(ResultEnum.USER_USERNAME_OR_PASSWORD_EMPTY);
        }
        String ip = IpUtils.getIpAddr(request);
        String limitCount = redisUtils.get(LoginKey.loginLimitCount.getPrefix() + RedisConstants.SEGMENTATION + ip + RedisConstants.SEGMENTATION + username);
        if (StringUtils.isNotEmpty(limitCount) && Integer.parseInt(limitCount) >= RedisConstants.NUM_FIVE) {
            throw new LoginException(ResultEnum.USER_LOGIN_ERROR_MORE);
        }
        LambdaQueryWrapper<UserInfo> query = Wrappers.<UserInfo>lambdaQuery()
                .eq(UserInfo::getStatus, EStatus.ENABLE.getCode())
                .last(SysConf.LIMIT_ONE);
        query.and(qr -> qr.eq(UserInfo::getCode, username));
        UserInfo admin = userInfoService.getOne(query);
        if (admin == null) {
            //设置错误登录次数
            throw new LoginException(ResultEnum.USER_LOGIN_ERROR_MORE.getCode(), String.format(ERROR_MESSAGE, setLoginCommit(request, username)));
        } else {
            admin.setOperateTime(LocalDateTime.now());
            admin.updateById();
        }
        //对密码进行加盐加密验证，采用SHA-256 + 随机盐【动态加盐】 + 密钥对密码进行加密
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        boolean isPassword = encoder.matches(password, admin.getPassword());
        if (!isPassword) {
            //密码错误，返回提示信息
            throw new LoginException(ResultEnum.USER_LOGIN_ERROR_MORE.getCode(), String.format(ERROR_MESSAGE, setLoginCommit(request, username)));
        }
        return loginCommon(request, admin, ip, isRemember, instrId, source);
    }

    @Override
    public UserVo loginPhone(HttpServletRequest request, String phone, String code, Boolean isRemember, String wechatCode, Long instrId, String source) throws Exception {
        log.info("手机登录参数：phone:{}, code:{}, wechatCode:{}, instrId:{}", phone, code, wechatCode, instrId);
        if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) {
            throw new LoginException(ResultEnum.USER_PHONE_OR_CODE_EMPTY);
        }
        String ip = IpUtils.getIpAddr(request);
        // 查询系统中是否有当前手机号，没有则先注册
        UserInfo admin = userInfoService.getOne(Wrappers.<UserInfo>lambdaQuery()
                .eq(UserInfo::getPhone, phone)
                .eq(UserInfo::getStatus, EStatus.ENABLE.getCode())
                .eq(UserInfo::getDeleteFlag, 0));
        String aliCode = redisUtils.get(LoginKey.loginPhoneCode.getPrefix() + RedisConstants.SEGMENTATION + phone);
        if (!code.equals(aliCode)) {
            //密码错误，返回提示信息
            throw new LoginException(ResultEnum.USER_PHONE_OR_CODE_ERROR);
        }
        WxMaJscode2SessionResult session = null;
        if (StringUtils.isNotBlank(wechatCode)) {
            session = wxMaService.getUserService().getSessionInfo(wechatCode);
            log.info("wechat token response: {}, token: {}", JSONObject.toJSONString(session), wxMaService.getAccessToken());
            String openId = session.getOpenid();
            if (StringUtils.isEmpty(openId)) {
                throw new LoginException(ResultEnum.USER_NO_AUTH_EMPTY);
            }
        }
        if (admin == null) {
            admin = new UserInfo();
            // 新增数据
            UserInfoVo newUser = new UserInfoVo();
            newUser.setPhone(phone);
            newUser.setIntroUserId(instrId);
            newUser.setFromSource(source);
            if (session != null) {
                newUser.setWechatOpenid(session.getOpenid());
                newUser.setWechatUnionid(session.getUnionid());
            }
            UserInfoVo newUserVo = userInfoService.addUserInfo(newUser);
            // 如果邀请人不为空，给邀请人发送红包
            if (instrId != null && "gzh".equals(source)) {
                addInitCommission(newUserVo.getId(), instrId);
                DefaultAmountConfigVo defaultAmountConfigVo = defaultAmountConfigMapper.queryDefaultAmountConfigByCode("invite-redPacket");
                log.info("邀请人id: {}, 红包金额: {}", instrId, defaultAmountConfigVo.getAmount());
                RedPacketVo redPacketVo = new RedPacketVo();
                redPacketVo.setAmount(defaultAmountConfigVo.getAmount());
                redPacketVo.setActName("邀请红包");
                redPacketVo.setWishing("恭喜发财，大吉大利");
                redPacketVo.setRemark("邀请新人有礼！");
                redPacketVo.setInvitedUserId(newUserVo.getId());
                redPacketVo.setUserId(instrId);
                eventPublisher.publishEvent(new UserInviteRedPacketEvent(this, redPacketVo));
            }
            BeanUtils.copyProperties(newUserVo, admin);
        } else {
            if (session != null) {
                admin.setWechatOpenid(session.getOpenid());
                admin.setWechatUnionid(session.getUnionid());
            }
            admin.setOperateTime(LocalDateTime.now());
            admin.updateById();
        }
        String limitCount = redisUtils.get(LoginKey.loginLimitCount.getPrefix() + RedisConstants.SEGMENTATION + ip + RedisConstants.SEGMENTATION + admin.getId());
        if (StringUtils.isNotEmpty(limitCount) && Integer.parseInt(limitCount) >= RedisConstants.NUM_FIVE) {
            throw new LoginException(ResultEnum.USER_LOGIN_ERROR_MORE);
        }
//        UserVo redisUser = redisUtils.get(LoginKey.loginAdmin, ip + RedisConstants.SEGMENTATION + admin.getId(), UserVo.class);
//        String headers = request.getHeader(audience.getTokenHeader());
//        if (redisUser != null && StringUtils.isNotBlank(headers) && authToken(headers)) {
//            // TODO (majf) 2024/3/18 10:22 更新过期时间
//            return redisUser;
//        }
        return loginCommon(request, admin, ip, isRemember, instrId, source);
    }

    @Override
    public UserVo loginWechat(HttpServletRequest request, String code, Boolean isRemember, String phone, Long instrId, String source) throws Exception {
        if (StringUtils.isEmpty(code)) {
            throw new LoginException(ResultEnum.USER_NO_AUTH_EMPTY);
        }
        WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(code);
        log.info("wechat token response: {}, token: {}", JSONObject.toJSONString(session), wxMaService.getAccessToken());
        String openId = session.getOpenid();
        if (StringUtils.isEmpty(openId)) {
            throw new LoginException(ResultEnum.USER_NO_AUTH_EMPTY);
        }
        // 通过openid查询用户信息
        // 查询系统中是否有当前手机号，没有则先注册
        LambdaQueryWrapper<UserInfo> query = Wrappers.<UserInfo>lambdaQuery()
                .eq(UserInfo::getWechatOpenid, openId)
                .eq(UserInfo::getStatus, EStatus.ENABLE.getCode())
                .eq(UserInfo::getDeleteFlag, 0);
        if (StringUtils.isNotBlank(phone)) {
            query.eq(UserInfo::getPhone, phone);
        }
        UserInfo admin = userInfoService.getOne(query);
        String ip = IpUtils.getIpAddr(request);
        // 如果通过手机号、微信号查询没有值
        if (admin == null) {
            LambdaQueryWrapper<UserInfo> queryPhone = Wrappers.<UserInfo>lambdaQuery()
                    .eq(UserInfo::getPhone, phone)
                    .eq(UserInfo::getStatus, EStatus.ENABLE.getCode())
                    .eq(UserInfo::getDeleteFlag, 0);
            UserInfo phoneUser = userInfoService.getOne(queryPhone);
            if (phoneUser != null) {
                admin = phoneUser;
                phoneUser.setWechatOpenid(openId);
                phoneUser.setWechatUnionid(session.getUnionid());
                phoneUser.setOperateTime(LocalDateTime.now());
                phoneUser.updateById();
            } else {
                admin = new UserInfo();
                // 新增数据
                UserInfoVo newUser = new UserInfoVo();
                newUser.setWechatOpenid(openId);
                newUser.setWechatUnionid(session.getUnionid());
                newUser.setPhone(phone);
                newUser.setIntroUserId(instrId);
                newUser.setFromSource(source);
                UserInfoVo newUserVo = userInfoService.addUserInfo(newUser);
                BeanUtils.copyProperties(newUserVo, admin);
                if (instrId != null && "gzh".equals(source)) {
                    addInitCommission(newUserVo.getId(), instrId);
                    DefaultAmountConfigVo defaultAmountConfigVo = defaultAmountConfigMapper.queryDefaultAmountConfigByCode("invite-redPacket");
                    log.info("邀请人id: {}, 红包金额: {}", instrId, defaultAmountConfigVo.getAmount());
                    RedPacketVo redPacketVo = new RedPacketVo();
                    redPacketVo.setAmount(defaultAmountConfigVo.getAmount());
                    redPacketVo.setActName("邀请红包");
                    redPacketVo.setWishing("恭喜发财，大吉大利");
                    redPacketVo.setRemark("邀请新人有礼！");
                    redPacketVo.setUserId(instrId);
                    redPacketVo.setInvitedUserId(newUserVo.getId());
                    eventPublisher.publishEvent(new com.love.marriage.common.event.UserInviteRedPacketEvent(this, redPacketVo));
                }
            }
        } else {
            admin.setOperateTime(LocalDateTime.now());
            admin.updateById();
        }
        String limitCount = redisUtils.get(LoginKey.loginLimitCount.getPrefix() + RedisConstants.SEGMENTATION + ip + RedisConstants.SEGMENTATION + admin.getId());
        if (StringUtils.isNotEmpty(limitCount) && Integer.parseInt(limitCount) >= RedisConstants.NUM_FIVE) {
            throw new LoginException(ResultEnum.USER_LOGIN_ERROR_MORE);
        }
        return loginCommon(request, admin, ip, isRemember, instrId, source);
    }

    private UserVo loginCommon(HttpServletRequest request, UserInfo admin, String ip, Boolean isRemember, Long instrId, String source) throws Exception {
        String uuid = StringUtils.getUUID();
        UserProfileInfoVo dbProfile = userProfileInfoService.queryUserProfileInfoByUserId(admin.getId());
        if (dbProfile == null && admin.getId() != 1) {
            UserProfileInfoVo userProfileInfoVo = new UserProfileInfoVo();
            userProfileInfoVo.setUserId(admin.getId());
            userProfileInfoVo.setContactPhone(admin.getPhone());
            userProfileInfoVo.setGender("2");
            userProfileInfoVo.setFirstFlag(true);
            userProfileInfoService.addUserProfileInfo(userProfileInfoVo);
        }
        UserVo userVo = userInfoService.queryUser(admin.getId());
        userVo.setToken(uuid);
//        //保存登录信息
        UserLoginInfo userLoginInfo = saveLoginLog(request, admin, uuid, ip, isRemember, source);
        //不返回密码到前端
        long expiration = isRemember != null && isRemember ? isRememberMeExpiresSecond : audience.getExpiresSecond();
        redisUtils.setEx(LoginKey.loginUuid, userLoginInfo.getTokenId(), userLoginInfo.getToken(), 0, TimeUnit.SECONDS);
        redisUtils.setEx(LoginKey.loginAdmin, userLoginInfo.getToken(), JSONObject.toJSONString(userLoginInfo), 0, TimeUnit.SECONDS);
        redisUtils.setEx(LoginKey.loginAdmin, ip + RedisConstants.SEGMENTATION + userVo.getId(), JSONObject.toJSONString(userLoginInfo), 0, TimeUnit.SECONDS);
        // 添加用户到im中
        // 开一个新线程
        // 使用CompletableFuture进行异步调用
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        CompletableFuture.runAsync(() -> {
            try {
                RequestContextHolder.setRequestAttributes(attributes);
                chatService.addNewUser(admin.getId());
            } catch (Exception e) {
                // 记录异常，便于调试
                e.printStackTrace();
            }
        });
        return userVo;
    }

    private UserLoginInfo saveLoginLog(HttpServletRequest request, UserInfo admin, String uuid, String ip, Boolean isRemember, String source) {
        String roleName = "";
        long expiration = isRemember != null && isRemember ? isRememberMeExpiresSecond : audience.getExpiresSecond();
        String jwtToken = jwtTokenUtils.createJwt(String.valueOf(admin.getId()), admin.getId(), roleName, audience.getClientId(), audience.getName()
                , -1, audience.getBase64Secret());
        Map<String, String> map = null;
        String location = null;
        try {
            map = IpUtils.getOsAndBrowserInfo(request);
            location = IpUtils.getCityInfo(ip);
            log.info("ip:{},地址：{}", ip, location);
        } catch (Exception e) {
            log.error("获取ip地址和设备信息失败：{}", e.getMessage());
        }
        String token = audience.getTokenHead() + jwtToken;
        assert map != null;
        String os = map.get(SysConf.OS);
        String browser = map.get(SysConf.BROWSER);
        UserLoginInfo userLoginInfo = new UserLoginInfo();
        UserLoginInfo userLogin = userLoginInfo
                .setUserId(admin.getId())
                .setLastLoginTime(LocalDateTime.now())
                .setTokenId(uuid)
                .setToken(token)
                .setOs(os)
                .setBroswer(browser)
                .setLoginIp(ip)
                .setLoginLocation(location)
                .setFromSource(source); // Set the source parameter
        // 设置认证信息到SecurityContextHolder
        UserDetails userDetails = SecurityUserFactory.create(userInfoService.getById(admin.getId()));
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, new ArrayList<>());
        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        userLogin.insert();
        return userLogin;
    }

    /**
     * param request
     * param username 登录名称
     * description: 设置登录限制，返回剩余次数
     * author: alex
     * return: java.lang.Integer
     */
    private Integer setLoginCommit(HttpServletRequest request, String username) throws Exception {
        String ip = IpUtils.getIpAddr(request);
        String loginCountKey = LoginKey.loginLimitCount.getPrefix() + RedisConstants.SEGMENTATION + ip +
                RedisConstants.SEGMENTATION + username;
        String count = redisUtils.get(loginCountKey);
        int surplusCount = RedisConstants.NUM_FIVE;
        int exTime = 30;
        if (StringUtils.isNotEmpty(count)) {
            int curCount = Integer.parseInt(count) + 1;
            surplusCount -= curCount;
            redisUtils.setEx(loginCountKey, Integer.toString(curCount), exTime, TimeUnit.MINUTES);
        } else {
            surplusCount -= 1;
            redisUtils.setEx(loginCountKey, String.valueOf(RedisConstants.NUM_ONE), exTime, TimeUnit.MINUTES);
        }
        return surplusCount;
    }

    @Override
    public Result<Boolean> logout(HttpServletRequest request) {
        String uuidToken = request.getHeader(audience.getTokenHeader());
        if (StringUtils.isEmpty(uuidToken)) {
            return Result.error(ResultEnum.PARAM_ERROR);
        } else {
            // 获取在线用户信息
            String barToken = redisUtils.get(LoginKey.loginUuid, uuidToken);
            redisUtils.delete(LoginKey.loginUuid, uuidToken);
            // 移除Redis中的用户
            redisUtils.delete(LoginKey.loginToken, barToken);
            SecurityContextHolder.clearContext();
            return Result.success();
        }
    }

    @Override
    public Boolean authToken(String uuidToken) {
        String barToken = redisUtils.get(LoginKey.loginUuid, uuidToken);
        if (StringUtils.isEmpty(barToken) || !barToken.startsWith(audience.getTokenHead())) {
            return false;
        }
        // 私钥
        String base64Secret = audience.getBase64Secret();
        String token = barToken.substring(audience.getTokenHead().length());
        //校验token
        if (StringUtils.isEmpty(token) || jwtTokenUtils.isExpiration(token, base64Secret)) {
            return false;
        }
        refreshToken(token, base64Secret, uuidToken, barToken);
        // 获取在线的管理员信息
        String id = jwtTokenUtils.getUsername(token, base64Secret);
        SecurityContextHolder.getContext().getAuthentication();
        if (id != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            // 通过用户名加载SpringSecurity用户
            UserDetails userDetails = SecurityUserFactory.create(userInfoService.getById(id));
            // 校验Token的有效性
            if (jwtTokenUtils.validateToken(token, userDetails, base64Secret)) {
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                //以后可以security中取得SecurityUser信息
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        return true;
    }

    /**
     * param: token
     * param: base64Secret
     * param: uuidToken
     * param: barToken
     * description: 判断是否需要刷新对应的token数据
     * author:      majf
     * return:      void
     */
    private void refreshToken(String token, String base64Secret, String uuidToken, String barToken) {
        taskExecutor.execute(() -> {
            Date expirationDate = jwtTokenUtils.getExpiration(token, base64Secret);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//            // 得到两个日期相差的间隔，秒
//            long survivalSecond = DateUtils.diffSecondByTwoDays(DateUtils.getTimeStr(LocalDateTime.now()), sdf.format(expirationDate));
//            // 而旧的Token将会在不久之后从Redis中过期,当存活时间小于更新时间，那么将颁发新的Token到客户端，同时重置新的过期时间
//            if (survivalSecond < audience.getRefreshSecond()) {
//                //生成新的token
//                String newToken = audience.getTokenHead() + jwtTokenUtils.refreshToken(token, base64Secret, -1);
//                // TODO (majf) 2024/2/21 15:04 多次后是否需要修改uuid信息
//                redisUtils.setEx(LoginKey.loginUuid, uuidToken, newToken, audience.getExpiresSecond(), TimeUnit.SECONDS);
//            }
        });
    }
    private void addInitCommission (Long userId, Long instrId) {
        // 添加回佣记录
        CommissionInfoVo newInfo = new CommissionInfoVo();
        newInfo.setUserId(userId);
        newInfo.setReferrerId(instrId);
        newInfo.setAmount(BigDecimal.ZERO);
        newInfo.setStatus(1);
        newInfo.setOrderId(null);
        newInfo.setCommissionType("init");
        commissionInfoService.addCommissionInfo(newInfo);
        // 如果介绍人还有介绍人，则添加记录
        UserInfoVo introUser = userInfoMapper.queryUserInfo(instrId);
        if (introUser != null && introUser.getIntroUserId() != null) {
            CommissionInfoVo introCommission = new CommissionInfoVo();
            introCommission.setUserId(instrId);
            introCommission.setReferrerId(introUser.getIntroUserId());
            introCommission.setAmount(BigDecimal.ZERO);
            introCommission.setStatus(1);
            introCommission.setOrderId(null);
            introCommission.setCommissionType("init");
            commissionInfoService.addCommissionInfo(introCommission);
        }
    }
}
